#include "shapes3d.h"
#include "gl\glfw.h"
#define _USE_MATH_DEFINES
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327
#endif



namespace Shapes3D
{

/* Rim, body, lid, and bottom data must be reflected in x and
   y; handle and spout data across the y axis only.  */
static int patchdata[][16] =
{
    /* rim */
  {102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11,
    12, 13, 14, 15},
    /* body */
  {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27},
  {24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36,
    37, 38, 39, 40},
    /* lid */
  {96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101,
    101, 0, 1, 2, 3,},
  {0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112,
    113, 114, 115, 116, 117},
    /* bottom */
  {118, 118, 118, 118, 124, 122, 119, 121, 123, 126,
    125, 120, 40, 39, 38, 37},
    /* handle */
  {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
    53, 54, 55, 56},
  {53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
    28, 65, 66, 67},
    /* spout */
  {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
    80, 81, 82, 83},
  {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
    92, 93, 94, 95}
};
static double cpdata[][3] =
{
    {0.2, 0, 2.7}, {0.2, -0.112, 2.7}, {0.112, -0.2, 2.7}, {0,
    -0.2, 2.7}, {1.3375, 0, 2.53125}, {1.3375, -0.749, 2.53125},
    {0.749, -1.3375, 2.53125}, {0, -1.3375, 2.53125}, {1.4375,
    0, 2.53125}, {1.4375, -0.805, 2.53125}, {0.805, -1.4375,
    2.53125}, {0, -1.4375, 2.53125}, {1.5, 0, 2.4}, {1.5, -0.84,
    2.4}, {0.84, -1.5, 2.4}, {0, -1.5, 2.4}, {1.75, 0, 1.875},
    {1.75, -0.98, 1.875}, {0.98, -1.75, 1.875}, {0, -1.75,
    1.875}, {2, 0, 1.35}, {2, -1.12, 1.35}, {1.12, -2, 1.35},
    {0, -2, 1.35}, {2, 0, 0.9}, {2, -1.12, 0.9}, {1.12, -2,
    0.9}, {0, -2, 0.9}, {-2, 0, 0.9}, {2, 0, 0.45}, {2, -1.12,
    0.45}, {1.12, -2, 0.45}, {0, -2, 0.45}, {1.5, 0, 0.225},
    {1.5, -0.84, 0.225}, {0.84, -1.5, 0.225}, {0, -1.5, 0.225},
    {1.5, 0, 0.15}, {1.5, -0.84, 0.15}, {0.84, -1.5, 0.15}, {0,
    -1.5, 0.15}, {-1.6, 0, 2.025}, {-1.6, -0.3, 2.025}, {-1.5,
    -0.3, 2.25}, {-1.5, 0, 2.25}, {-2.3, 0, 2.025}, {-2.3, -0.3,
    2.025}, {-2.5, -0.3, 2.25}, {-2.5, 0, 2.25}, {-2.7, 0,
    2.025}, {-2.7, -0.3, 2.025}, {-3, -0.3, 2.25}, {-3, 0,
    2.25}, {-2.7, 0, 1.8}, {-2.7, -0.3, 1.8}, {-3, -0.3, 1.8},
    {-3, 0, 1.8}, {-2.7, 0, 1.575}, {-2.7, -0.3, 1.575}, {-3,
    -0.3, 1.35}, {-3, 0, 1.35}, {-2.5, 0, 1.125}, {-2.5, -0.3,
    1.125}, {-2.65, -0.3, 0.9375}, {-2.65, 0, 0.9375}, {-2,
    -0.3, 0.9}, {-1.9, -0.3, 0.6}, {-1.9, 0, 0.6}, {1.7, 0,
    1.425}, {1.7, -0.66, 1.425}, {1.7, -0.66, 0.6}, {1.7, 0,
    0.6}, {2.6, 0, 1.425}, {2.6, -0.66, 1.425}, {3.1, -0.66,
    0.825}, {3.1, 0, 0.825}, {2.3, 0, 2.1}, {2.3, -0.25, 2.1},
    {2.4, -0.25, 2.025}, {2.4, 0, 2.025}, {2.7, 0, 2.4}, {2.7,
    -0.25, 2.4}, {3.3, -0.25, 2.4}, {3.3, 0, 2.4}, {2.8, 0,
    2.475}, {2.8, -0.25, 2.475}, {3.525, -0.25, 2.49375},
    {3.525, 0, 2.49375}, {2.9, 0, 2.475}, {2.9, -0.15, 2.475},
    {3.45, -0.15, 2.5125}, {3.45, 0, 2.5125}, {2.8, 0, 2.4},
    {2.8, -0.15, 2.4}, {3.2, -0.15, 2.4}, {3.2, 0, 2.4}, {0, 0,
    3.15}, {0.8, 0, 3.15}, {0.8, -0.45, 3.15}, {0.45, -0.8,
    3.15}, {0, -0.8, 3.15}, {0, 0, 2.85}, {1.4, 0, 2.4}, {1.4,
    -0.784, 2.4}, {0.784, -1.4, 2.4}, {0, -1.4, 2.4}, {0.4, 0,
    2.55}, {0.4, -0.224, 2.55}, {0.224, -0.4, 2.55}, {0, -0.4,
    2.55}, {1.3, 0, 2.55}, {1.3, -0.728, 2.55}, {0.728, -1.3,
    2.55}, {0, -1.3, 2.55}, {1.3, 0, 2.4}, {1.3, -0.728, 2.4},
    {0.728, -1.3, 2.4}, {0, -1.3, 2.4}, {0, 0, 0}, {1.425,
    -0.798, 0}, {1.5, 0, 0.075}, {1.425, 0, 0}, {0.798, -1.425,
    0}, {0, -1.5, 0.075}, {0, -1.425, 0}, {1.5, -0.84, 0.075},
    {0.84, -1.5, 0.075}
};

/* This "fastTeapot" routine is faster than glutSolidTeapot. */
void FastTeapot(size_t grid, double scale)
{
  float p[4][4][3], q[4][4][3], r[4][4][3], s[4][4][3];
  long i, j, k, l;

  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_MAP2_VERTEX_3);
  glRotatef(270.0, 1.0, 0.0, 0.0);
  glScaled(0.5 * scale, 0.5 * scale, 0.5 * scale);
  glTranslatef(0.0, 0.0, -1.5);
  for (i = 0; i < 10; i++) {
    for (j = 0; j < 4; j++) {
      for (k = 0; k < 4; k++) {
        for (l = 0; l < 3; l++) {
          p[j][k][l] = (float)cpdata[patchdata[i][j * 4 + k]][l];
          q[j][k][l] = (float)cpdata[patchdata[i][j * 4 + (3 - k)]][l];
          if (l == 1)
            q[j][k][l] *= -1.0;
          if (i < 6) {
            r[j][k][l] =
              (float)cpdata[patchdata[i][j * 4 + (3 - k)]][l];
            if (l == 0)
              r[j][k][l] *= -1.0;
            s[j][k][l] = (float)cpdata[patchdata[i][j * 4 + k]][l];
            if (l == 0)
              s[j][k][l] *= -1.0;
            if (l == 1)
              s[j][k][l] *= -1.0;
          }
        }
      }
    }
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4,
      &p[0][0][0]);
    glMapGrid2f(grid, 0.0, 1.0, grid, 0.0, 1.0);
    glEvalMesh2(GL_FILL, 0, grid, 0, grid);
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4,
      &q[0][0][0]);
    glEvalMesh2(GL_FILL, 0, grid, 0, grid);
    if (i < 6) {
      glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4,
        &r[0][0][0]);
      glEvalMesh2(GL_FILL, 0, grid, 0, grid);
      glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4,
        &s[0][0][0]);
      glEvalMesh2(GL_FILL, 0, grid, 0, grid);
    }
  }

	glDisable(GL_AUTO_NORMAL);
	glDisable(GL_MAP2_VERTEX_3);
}

void DrawCube(float size)
{
	// method taken from cube.c by Mark J. Kilgard, 1997
	// http://www.opengl.org/resources/code/samples/glut_examples/examples/cube.c
	float n[6][3] = {  // Normals for the 6 faces of a cube. 
  {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0},
  {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0} };

	int faceIndices[6][4] = {  /* Vertex indices for the 6 faces of a cube. */
  {0, 1, 2, 3}, {3, 2, 6, 7}, {7, 6, 5, 4},
  {4, 5, 1, 0}, {5, 6, 2, 1}, {7, 4, 0, 3} };


	float v[8][3];  // Will be filled in with X,Y,Z vertexes.
  v[0][0] = v[1][0] = v[2][0] = v[3][0] = -size;
  v[4][0] = v[5][0] = v[6][0] = v[7][0] = size;
  v[0][1] = v[1][1] = v[4][1] = v[5][1] = -size;
  v[2][1] = v[3][1] = v[6][1] = v[7][1] = size;
  v[0][2] = v[3][2] = v[4][2] = v[7][2] = size;
  v[1][2] = v[2][2] = v[5][2] = v[6][2] = -size;

  for (int i=0; i < 6; i++) {
    glBegin(GL_QUADS);
    glNormal3fv(&n[i][0]);
    glVertex3fv(&v[faceIndices[i][0]][0]);
    glVertex3fv(&v[faceIndices[i][1]][0]);
    glVertex3fv(&v[faceIndices[i][2]][0]);
    glVertex3fv(&v[faceIndices[i][3]][0]);
    glEnd();
  }

}


void CreateBox( float x, float y, float z, float width, float height,  float length, float uscale, float vscale)
{
	// This function is a lot like our CreateSkyBox() function in the sky box tutorial.
	// It creates a box with specific dimension around a center point.
	// The first parameter is the texture ID to render from, or < 0 for no texture.
	// The next parameters are the center position, the dimensions and UV scaling.
	// This will be used to render the spinnin cube we see rendered to the texture.
	
	// Check if we have a valid texture ID, then bind that texture to our box
	// This centers the box around (x, y, z)
	x = x - width  / 2;
	y = y - height / 2;
	z = z - length / 2;

	// Start drawing the side as a QUAD
	glBegin(GL_QUADS);		
		
		// Assign the texture coordinates and vertices for the BACK Side
		glTexCoord2f(0.0f,     0.0f); glVertex3f(x,			y,			z);
		glTexCoord2f(0.0f,   vscale); glVertex3f(x,			y + height, z);
		glTexCoord2f(uscale, vscale); glVertex3f(x + width, y + height, z); 
		glTexCoord2f(uscale,   0.0f); glVertex3f(x + width, y,			z);
	
		// Assign the texture coordinates and vertices for the FRONT Side
		glTexCoord2f(uscale,   0.0f); glVertex3f(x,			y,			z + length);
		glTexCoord2f(uscale, vscale); glVertex3f(x,			y + height, z + length);
		glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y + height, z + length); 
		glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y,			z + length);
	
		// Assign the texture coordinates and vertices for the BOTTOM Side
		glTexCoord2f(uscale,   0.0f); glVertex3f(x,			y,			z);
		glTexCoord2f(uscale, vscale); glVertex3f(x,			y,			z + length);
		glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y,			z + length); 
		glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y,			z);

		// Assign the texture coordinates and vertices for the TOP Side
		glTexCoord2f(uscale, vscale); glVertex3f(x,			y + height,	z);
		glTexCoord2f(uscale,   0.0f); glVertex3f(x,			y + height,	z + length);
		glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y + height, z + length); 
		glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y + height, z);
	
		// Assign the texture coordinates and vertices for the LEFT Side
		glTexCoord2f(uscale,   0.0f); glVertex3f(x,			y,			z);
		glTexCoord2f(0.0f,     0.0f); glVertex3f(x,			y,			z + length);
		glTexCoord2f(0.0f,   vscale); glVertex3f(x,			y + height,	z + length); 
		glTexCoord2f(uscale, vscale); glVertex3f(x,			y + height,	z);

		// Assign the texture coordinates and vertices for the RIGHT Side
		glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y,			z);
		glTexCoord2f(uscale,   0.0f); glVertex3f(x + width, y,			z + length);
		glTexCoord2f(uscale, vscale); glVertex3f(x + width, y + height,	z + length); 
		glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y + height,	z);

	// Stop drawing quads
	glEnd();
}

void DrawQuad(float s)
{
	glBegin (GL_QUADS);
	glTexCoord2f (0.0, 0.0);glVertex3f (-s, -s, 0.0);
	glTexCoord2f (1.0, 0.0);glVertex3f (s, -s, 0.0);
	glTexCoord2f (1.0, 1.0);glVertex3f (s, s, 0.0);
	glTexCoord2f (0.0, 1.0);glVertex3f (-s, s, 0.0);
	glEnd ();
}


// For best results, put this in a display list
// Draw a sphere at the origin
void DrawSphere(float fRadius, int iSlices, int iStacks)
{
	GLfloat drho = (GLfloat)(3.141592653589) / (GLfloat) iStacks;
	GLfloat dtheta = 2.0f * (GLfloat)(3.141592653589) / (GLfloat) iSlices;
	GLfloat ds = 1.0f / (GLfloat) iSlices;
	GLfloat dt = 1.0f / (GLfloat) iStacks;
	GLfloat t = 1.0f;	
	GLfloat s = 0.0f;
	GLint i, j;     // Looping variables

	for (i = 0; i < iStacks; i++) 
	{
		GLfloat rho = (GLfloat)i * drho;
		GLfloat srho = (GLfloat)(sin(rho));
		GLfloat crho = (GLfloat)(cos(rho));
		GLfloat srhodrho = (GLfloat)(sin(rho + drho));
		GLfloat crhodrho = (GLfloat)(cos(rho + drho));

		// Many sources of OpenGL sphere drawing code uses a triangle fan
		// for the caps of the sphere. This however introduces texturing 
		// artifacts at the poles on some OpenGL implementations
		glBegin(GL_TRIANGLE_STRIP);
		s = 0.0f;
		for ( j = 0; j <= iSlices; j++) 
		{
			GLfloat theta = (j == iSlices) ? 0.0f : j * dtheta;
			GLfloat stheta = (GLfloat)(-sin(theta));
			GLfloat ctheta = (GLfloat)(cos(theta));

			GLfloat x = stheta * srho;
			GLfloat y = ctheta * srho;
			GLfloat z = crho;

			glTexCoord2f(s, t);
			glNormal3f(x, y, z);
			glVertex3f(x * fRadius, y * fRadius, z * fRadius);

			x = stheta * srhodrho;
			y = ctheta * srhodrho;
			z = crhodrho;
			glTexCoord2f(s, t - dt);
			s += ds;
			glNormal3f(x, y, z);
			glVertex3f(x * fRadius, y * fRadius, z * fRadius);
		}
		glEnd();

		t -= dt;
	}
}

	void DrawWireGrid(float size)
	{
		float halfSize = size / 2;
		glBegin(GL_LINES);
		for(int i=int(-halfSize); i != int(halfSize); i++)
		{
			glVertex3f((float)i,0,halfSize);
			glVertex3f((float)i+1,0,-halfSize);

			glVertex3f(halfSize,0,(float)i);
			glVertex3f(-halfSize,0,(float)i+1.0f);
		}
		glEnd();
	}

void DrawCylinder(float radius,float length,int segments, bool capped)
{
	float a=0;
	float angInc = float(2*M_PI)/float(segments-1.0f);
	glBegin(GL_TRIANGLE_STRIP);
	for(int i=0; i<segments; i++)
	{
		float x = cos(a)*radius;
		float z = sin(a)*radius;

		// Calc normal.
		float ax = x+a; //a;
		float ay = length;
		float az = z;
		float bx = x; // a+angInc;
		float by = 0;
		float bz = z;
		float cx = ay*bz - az*by;
		float cy = az*bx - ax*bz;
		float cz = ax*by - ay*bx;
		float len = sqrt(cx*cx + cy*cy + cz*cz);
		glNormal3f(cx/len,cy/len,cz/len);

		glTexCoord2d(a/(2*M_PI),0); glVertex3d(x,0,z);
		glTexCoord2d(a/(2*M_PI),1); glVertex3d(x,length,z);

		a += angInc;
	}
	glEnd();

	if(capped)
	{
		// Top
		a=0;
		glNormal3f(0,1,0);
		glFrontFace(GL_CW);
		glBegin(GL_TRIANGLE_FAN);
		for(int i=0; i<segments; i++)
		{
			double x = cos(a)*radius;
			double z = sin(a)*radius;
			glVertex3d(x,length,z);
			a += angInc; 
		} 
		glEnd();
		glFrontFace(GL_CCW); // standard

		// Bottom
		a=0;
		glNormal3f(0,-1,0);
		glBegin(GL_TRIANGLE_FAN);
		for(int i=0; i<segments; i++)
		{
			double x = cos(a)*radius;
			double z = sin(a)*radius;
			glVertex3d(x,0,z);
			a += angInc; 
		}  
		glEnd();
	}
}



	void DrawCone(float baseRadius, float length, int segs)
	{
		float a=0.0f;
		float ainc = 2.0f*3.14159f/float(segs);

		glBegin(GL_TRIANGLE_FAN);
		glVertex3f(0,0,0);
		for(int i=0; i<segs+1; i++)
		{
			float x = baseRadius*cos(a);
			float y = baseRadius*sin(a);

			glTexCoord2f(x,y); 
			glVertex3f(x,y,length);
			a+=ainc;
		}
		glEnd();
	}

// Taken from NeHe 48, check www.nehe.gamedev.net
void DrawTorus(float MinorRadius, float MajorRadius)					// Draw A Torus With Normals
{
	int i, j;
	glBegin( GL_TRIANGLE_STRIP );									// Start A Triangle Strip
	for (i=0; i<20; i++ )										// Stacks
	{
		for (j=-1; j<20; j++)									// Slices
		{
			float wrapFrac = (j%20)/(float)20;
			float phi = float(2*M_PI)*wrapFrac;
			float sinphi = float(sin(phi));
			float cosphi = float(cos(phi));

			float r = MajorRadius + MinorRadius*cosphi;

			glNormal3f(float(sin(2*M_PI*(i%20+wrapFrac)/(float)20))*cosphi, sinphi, float(cos(2*M_PI*(i%20+wrapFrac)/(float)20))*cosphi);
			//glTexCoord2f(cosphi,sinphi);
			glVertex3f(float(sin(2*M_PI*(i%20+wrapFrac)/(float)20))*r,MinorRadius*sinphi,float(cos(2*M_PI*(i%20+wrapFrac)/(float)20))*r);

			glNormal3f(float(sin(2*M_PI*(i+1%20+wrapFrac)/(float)20))*cosphi, sinphi, float(cos(2*M_PI*(i+1%20+wrapFrac)/(float)20))*cosphi);
			glVertex3f(float(sin(2*M_PI*(i+1%20+wrapFrac)/(float)20))*r,MinorRadius*sinphi,float(cos(2*M_PI*(i+1%20+wrapFrac)/(float)20))*r);
		}
	}
	glEnd();														// Done Torus
}

}//eof namespace